hErmol crackme v1.0 [zip]

Ici une autre manière de protéger un programme : le keyfile, c'est un fichier qui est utilisé au démarrage du programme et qui indique si celui-ci est enregistré ou non.

On exécute le programme et on aperçoit une très belle fenêtre nous disant que nous sommes en mode shareware :-/ On le désassemble, et là on voit 3 choses...

Tout d'abord un énorme vide, beaucoup de valeurs hexadécimales et un peu de code... Le programme ne serait-il pas compressé ? On ouvre PEiD, 'Open File', on sélectionne notre crackme, et on voit s'afficher 'UPX 0.89.6 - 1.02 / 1.05 - 1.22 -> Markus & Lazlo'.

On pourrait faire du manual unpacking comme c'est de l'UPX, mais bon, il existe des programmes pour ça, donc autant s'en servir, c'est là pour ça. On ouvre ProcDump, puis 'Unpack', on choisit 'UPX' (ben oui comme c'est de l'UPX, ça paraît normal, non ?). On ouvre le programme compressé, on attend qu'il s'ouvre dans la barre des tâches, et on fait 'OK'. On sauvegarde ensuite le fichier décompressé.

On réouvre notre fichier avec IDA, on regarde à peu près et on voit beaucoup beaucoup beaucoup, mais attention, quand je dis beaucoup beaucoup, c'est vraiment beaucoup beaucoup ! donc on voit beaucoup de nop...
arf, ce qui rend le code pas très lisible, car entre chaque instruction, il y a environ 200 lignes de nop, alors pour tracer... pas grave, on va agir autrement, ça va nous retarder, mais ça sera plus facile pour plus tard. On regarde d'abord si le programme appelle quelques fonctions intéressantes, on va dans 'Jump', 'Jump to name...', et là on cherche une fonction qui ouvre un fichier, généralement c'est l'API CreateFile.

Hum pas de CreateFile, hum je sais pas si vous avez vu, mais y a une autre API intéressante là, non ? Et _lopen ? Donc on double-clique dessus, et on arrive à ceci :

.idata:004070BC .......; HFILE __stdcall _lopen(LPCSTR lpPathName,int iReadWrite)
.idata:004070BC .......extrn _lopen:dword

Mais à aucun moment le programme ne l'appelle, hum, bizarre... pourtant ça convient très bien à notre crackme cette fonction... Il faut savoir que certains packers transforment le code en data ce qui le rend donc illisible, alors allons donc voir le PE du programme, on ouvre ProcDump, 'PE Editor', et on ouvre notre fichier compressé, ensuite 'Sections', et là quelque chose qui devrait vous sauter aux yeux, non ?

Bon je vous aide, dans le PE, une section code est 0x??????20, et là la section code est 0x??????80 qui correspond à des données non initialisées, alors, je vous avais pas dit ça ?

Bon on change la section .code de E0000080 en E0000020 (pour cela clic-droit sur .code, 'Edit section', 'Section Characteristics'...). On fait 'OK', et on recommence.
(NDOracle : Pour en savoir plus sur la structure du PE et les sections reportez vous au tutorial d'Anubis présent dans ce Memento).

Hum déjà il est plus long à désassembler, bon signe. Un petit 'Jump to name _lopen' et on voit du changement !

.code:0040485A .......; HFILE __stdcall _lopen(LPCSTR lpPathName,int iReadWrite)
.code:0040485A _lopen proc near ; CODE XREF: sub_4013B0+6C8|p
.code:0040485A .......jmp ds:__imp__lopen
.code:0040485A _lopen endp

Maintenant nous allons extraire le code assembleur qui nous intéresse pour ensuite supprimer tous les NOP, on pourra donc avoir un code désassemblé plus lisible :o) On va donc commencer à copier à l'adresse 401A78, mais autant prendre les arguments, donc on va prendre un peu plus haut.
En supprimant ensuite tous les NOP, voici ce qu'on obtient :

0040187D 6A 02 ........PUSH 2
004019AB 68 29514000 ..PUSH _crackme.00405129 ; ASCII "CRACKME.KEY"
00401A78 E8 DD2D0000 ..CALL <JMP.&kernel32._lopen>
00401A7D 83F8 FF ......CMP EAX,-1
00401A80 0F85 5D020000 JNZ _crackme.00401CE3
00401A86 E9 BE100000 ..JMP _crackme.00402B49
00401CE3 A3 E3664000 ..MOV DWORD PTR DS:[4066E3],EAX
00401DB0 33C9 .........XOR ECX,ECX
00401DB2 1E ...........PUSH DS
00401DB3 07 ...........POP ES ...................; Modification of segment register
00401DB4 BE AE534000 ..MOV ESI,_crackme.004053AE
00401E81 87F3 .........XCHG EBX,ESI
00401F4B 6A 01 ........PUSH 1
00401F4D 68 9A674000 ..PUSH _crackme.0040679A
00401F52 FF35 E3664000 PUSH DWORD PTR DS:[4066E3]
00401F58 E8 09290000 ..CALL <JMP.&kernel32._hread>
00401F5D 83F8 01 ......CMP EAX,1
00401F60 0F85 6D110000 JNZ _crackme.004030D3
0040202E 33C9 .........XOR ECX,ECX
004020F8 BF 9A674000 ..MOV EDI,_crackme.0040679A
004021C5 92 ...........XCHG EAX,EDX
0040228E C1E0 04 ......SHL EAX,4
00402359 C1E0 08 ......SHL EAX,8
00402424 C1E0 08 ......SHL EAX,8
004024EF C1E0 04 ......SHL EAX,4
004025BA C1E0 08 ......SHL EAX,8
00402685 87CA .........XCHG EDX,ECX
0040274F 031D E9664000 ADD EBX,DWORD PTR DS:[4066E9]
0040281D 87F3 .........XCHG EBX,ESI
004028E7 46 ...........INC ESI
004029B0 8A06 .........MOV AL,BYTE PTR DS:[ESI]
004029B2 AF ...........SCAS DWORD PTR ES:[EDI]
00402A7B 0F84 00F4FFFF JE _crackme.00401E81
00402B49 92 ...........XCHG EAX,EDX
00402C12 C1E0 02 ......SHL EAX,2
00402CDD C1E0 08 ......SHL EAX,8
00402DA8 C1E0 03 ......SHL EAX,3
00402E73 C1E0 04 ......SHL EAX,4
00402F3E C1E0 06 ......SHL EAX,6
00403009 87CA .........XCHG EDX,ECX
0040319B BE AE534000 ..MOV ESI,_crackme.004053AE
004031A0 2BDE .........SUB EBX,ESI
0040326A 81FB 00080000 CMP EBX,800
00403270 75 09 ........JNZ SHORT _crackme.0040327B
00403274 E9 45030000 ..JMP _crackme.004035BE
0040327B 40 ...........INC EAX
0040327C 5E ...........POP ESI
0040327D 5F ...........POP EDI
0040327E 5B ...........POP EBX
0040327F C9 ...........LEAVE
00403280 C2 1000 ......RETN 10

On y comprend déjà beaucoup mieux, non ? Pas grave, on va étudier ça ensemble.
Nous allons reprendre le programme ligne par ligne :

0040187D 6A 02 ........PUSH 2
004019AB 68 29514000 ..PUSH _crackme.00405129 ; ASCII "CRACKME.KEY"
00401A78 E8 DD2D0000 ..CALL <JMP.&kernel32._lopen>

Tout d'abord on ouvre le fichier "CRACKME.KEY" en lecture/écriture (2).
00401DB4 BE AE534000 ..MOV ESI,_crackme.004053AE
00401E81 87F3 .........XCHG EBX,ESI


On initialise ESI à 004053AE qu'on a placé dans EBX en permutant les registres avec 'XCHG EBX,ESI'.
00401F4B 6A 01 ........PUSH 1
00401F4D 68 9A674000 ..PUSH _crackme.0040679A
00401F52 FF35 E3664000 PUSH DWORD PTR DS:[4066E3]
00401F58 E8 09290000 ..CALL <JMP.&kernel32._hread>


On va lire ensuite caractère par caractère (ce qui correspond au 'PUSH 1' en 00401F4B) le fichier. On verra ensuite que beaucoup de code ne sert à rien ici, ah là là vraiment pour embêter le monde hein ! ;op
0040274F 031D E9664000 ADD EBX,DWORD PTR DS:[4066E9]

On ajoute ensuite à EBX la valeur se trouvant à l'adresse 4066E9 qui est 0xFF.
0040281D 87F3 .........XCHG EBX,ESI
004028E7 46 ...........INC ESI


On repermute les registres, et on ajoute 1 à ESI, en fait c'est comme si on ajoutait 1 à EBX avant la permutation. On a en fait ajouté 0x100 en tout à la valeur se trouvant à l'origine dans ESI.
Vient ensuite une expression peut-être inconnue :
004029B0 8A06 .........MOV AL,BYTE PTR DS:[ESI]
004029B2 AF ...........SCAS DWORD PTR ES:[EDI]


Cette instruction permet de chercher si un caractère stocké dans AL/AX/EAX se trouve dans une chaîne pointée par EDI. On cherche tout simplement à vérifier si le caractère lu dans notre fichier correspond au caractère trouvé après s'être déplacé de 0x100 à partir de 004053AE.
00402A7B 0F84 00F4FFFF JE _crackme.00401E81

Si c'est le cas, on retourne plus haut pour lire le caractère suivant, et on fait la même opération.
00401F58 E8 09290000 ..CALL <JMP.&kernel32._hread>
00401F5D 83F8 01 ......CMP EAX,1
00401F60 0F85 6D110000 JNZ _crackme.004030D3


S'il n'y a plus de caractères à lire, on saute directement en 004030D3, qui est un NOP, donc l'instruction rencontrée sera en 0040319B.
0040319B BE AE534000 ..MOV ESI,_crackme.004053AE
004031A0 2BDE .........SUB EBX,ESI


On soustrait ensuite à EBX, la valeur d'origine, c'est-à-dire qu'on saura combien on a additionné.
0040326A 81FB 00080000 CMP EBX,800
00403270 75 09 ........JNZ SHORT _crackme.0040327B
00403274 E9 45030000 ..JMP _crackme.004035BE


Si on a additionné 0x800, c'est tout bon, et comme on ajoutait 0x100 à chaque passage, ça veut dire qu'on a lu 8 caractères, il ne reste plus qu'à les trouver.

Ce qui est très facile maintenant qu'on a compris comment fonctionnait le crackme. On est donc parti de l'adresse 004053AE, puis on a lu le caractère qui s'y trouvé tous les 100 octets plus loin. On va donc rechercher les octets suivants : 004054AE, 004055AE, 004056AE, 004057AE, 004058AE, 004059AE, 00405AAE, 00405BAE.
Et on y trouve : 0x89 , 0x8B , 0x05 , 0xE2 , 0x00 , 0x32 , 0x87 , 0x95.

On ouvre donc le fichier 'crackme.key' avec un éditeur hexadécimal, et on met ces valeurs : 898B 05E2 0032 8795, on sauvegarde, on relance le crackme, et le bon message s'affiche !

Vous avez donc vu que certaines instructions n'étaient pas utilisées, alors voici le code réellement utilisée :
0040187D 6A 02 ........PUSH 2
004019AB 68 29514000 ..PUSH _crackme.00405129 ; ASCII "CRACKME.KEY"
00401A78 E8 DD2D0000 ..CALL <JMP.&kernel32._lopen>
00401A7D 83F8 FF ......CMP EAX,-1
00401A80 0F85 5D020000 JNZ _crackme.00401CE3
00401A86 E9 BE100000 ..JMP _crackme.00402B49
00401CE3 A3 E3664000 ..MOV DWORD PTR DS:[4066E3],EAX
...
00401DB4 BE AE534000 ..MOV ESI,_crackme.004053AE
00401E81 87F3 .........XCHG EBX,ESI
00401F4B 6A 01 ........PUSH 1
00401F4D 68 9A674000 ..PUSH _crackme.0040679A
00401F52 FF35 E3664000 PUSH DWORD PTR DS:[4066E3]
00401F58 E8 09290000 ..CALL <JMP.&kernel32._hread>
00401F5D 83F8 01 ......CMP EAX,1
00401F60 0F85 6D110000 JNZ _crackme.004030D3
...
004020F8 BF 9A674000 ..MOV EDI,_crackme.0040679A
...
0040274F 031D E9664000 ADD EBX,DWORD PTR DS:[4066E9]
0040281D 87F3 .........XCHG EBX,ESI
004028E7 46 ...........INC ESI
004029B0 8A06 .........MOV AL,BYTE PTR DS:[ESI]
004029B2 AF ...........SCAS DWORD PTR ES:[EDI]
00402A7B 0F84 00F4FFFF JE _crackme.00401E81
...
0040319B BE AE534000 ..MOV ESI,_crackme.004053AE
004031A0 2BDE .........SUB EBX,ESI
0040326A 81FB 00080000 CMP EBX,800
00403270 75 09 ........JNZ SHORT _crackme.0040327B
00403274 E9 45030000 ..JMP _crackme.004035BE
0040327B 40 ...........INC EAX
0040327C 5E ...........POP ESI
0040327D 5F ...........POP EDI
0040327E 5B ...........POP EBX
0040327F C9 ...........LEAVE
00403280 C2 1000 ......RETN 10


Le début de ce tut est assez long, mais vous pouvez très bien y aller au feeling sans devoir unpacker le prog, vous tracez, vous regardez, vous cherchez, et vous arriverez au même résultat, mais la méthode décrite ici est "plus propre".